package jinyilw.tools.net;

import jinyilw.common.Operator;
import jinyilw.common.OperatorSets;
import jinyilw.common.database.JdbcTools;
import jinyilw.common.log.LogTools;
import jinyilw.tools.net.dev.CommandPacket;
import jinyilw.tools.net.dev.NetworkMessage;
import jinyilw.tools.net.dev.NetworkPacket;
import jinyilw.tools.net.dev.TCPClient;
import jinyilw.tools.net.file.FileTransferClient;

import java.io.File;
import java.io.IOException;
import java.util.Objects;

public class ClientTools implements Runnable
{
	private static volatile ClientTools defaultTools;

	private TCPParameter parameter;
	private volatile TCPClient client;
	private FileTransferClient fileTransferClient;
	private int fileTransferPort;
	private JdbcTools jdbcTools;
	private final int reTryTime = NetTools.debug ? 18000 : 60000;
	private volatile boolean threadRunning;
	private boolean connecting;
	private final Object lock = new Object();
	private final OperatorSets<Object> brokenOperators = new OperatorSets<>(true);
	private final OperatorSets<Object> reConnectOperators = new OperatorSets<>(true);
	private final OperatorSets<Object> receivePacketOperators = new OperatorSets<>(true);

	private boolean enableUpdate;
	private boolean autoUpdate;

	public ClientTools(TCPParameter parameter)
	{
		this.parameter = parameter;
	}

	public static ClientTools getDefaultTools()
	{
		if (defaultTools == null)
			synchronized (ClientTools.class)
			{
				if (defaultTools == null)
					defaultTools = new ClientTools(
							NetConfig.getDefaultTCPParameter());
			}
		return defaultTools;
	}

	public void bindJdbcTools(JdbcTools jdbcTools)
	{
		this.jdbcTools = jdbcTools;
	}

	public void addBrokenOperator(Operator<Object> operator)
	{
		brokenOperators.add(operator);
	}

	public void addBrokenRunner(Runnable runner)
	{
		if (runner != null)
			brokenOperators.add(o ->
			{
				new Thread(runner).start();
			});
	}

	public void addReConnectOperator(Operator<Object> operator)
	{
		reConnectOperators.add(operator);
	}

	public void addReConnectRunner(Runnable runner)
	{
		if (runner != null)
			reConnectOperators.add((Object o) ->
			{
				new Thread(runner).start();
			});
	}

	public void addReceivePacketOperator(Operator<Object> operator)
	{
		receivePacketOperators.add(operator);
	}

	public void sendMessage(String message)
	{
		if (client != null)
			try
			{
				client.sendPacket(new NetworkMessage(message));
			} catch (IOException ex)
			{
				ex.printStackTrace();
			}
	}

	public boolean sendCommand(CommandPacket commandPacket, int command)
	{
		if (commandPacket == null)
			return false;
		commandPacket.setCommandID((short) command);
		return sendCommand(commandPacket);
	}

	public boolean sendCommand(CommandPacket commandPacket)
	{
		if (client != null && commandPacket != null)
			return client.sendCommand(commandPacket);
		return false;
	}

	public void setParameter(TCPParameter param)
	{
		if (parameter != param)
		{
			parameter = param;
			if (threadRunning)
			{
				release();
				reConnect();
			}
		}
	}

	public void setParameter(String serverIP, int port)
	{
		if (Objects.equals(parameter.serverIP, serverIP) && parameter.port == port)
			return;
		parameter.serverIP = serverIP;
		parameter.port = port;
		if (threadRunning)
		{
			release();
			reConnect();
		}
	}

	synchronized TCPClient createClient()
	{
		if (client == null)
		{
			connecting = true;
			client = NetTools.createTCPClient(parameter);
			connecting = false;
			if (client != null)
				connectAct();
		}
		return client;
	}

	private void connectAct()
	{
		if (fileTransferClient != null)
		{
			fileTransferClient.setHostAddress(parameter.serverIP,
					fileTransferPort);
			client.setFileTransfer(fileTransferClient);
		}
		reConnectOperators.operate(this);
		if (enableUpdate && autoUpdate)
			UpdateTools.autoCheckUpdate(client);
	}

	public boolean isConnected()
	{
		return client != null;
	}

	public boolean isConnecting()
	{
		return connecting;
	}

	public synchronized boolean connect()
	{
		if (threadRunning)
			return true;
		if (client == null)
			createClient();
		threadRunning = true;
		new Thread(this).start();
		return isConnected();
	}

	public synchronized boolean reConnect()
	{
		if (jdbcTools != null)
			jdbcTools.reConnectInThread();
		if (!threadRunning)
			connect();
		else if (client == null)
		{
			createClient();
			synchronized (lock)
			{
				lock.notifyAll();
			}
		}
		return client != null;
	}

	public synchronized void release()
	{
		if (jdbcTools != null)
			jdbcTools.releaseConnectInThread();
		TCPClient c = client;
		client = null;
		if (c != null)
		{
			brokenOperators.operate(this);
			try
			{
				c.disconnect();
			} catch (IOException ignored)
			{
			}
		}
		if (fileTransferClient != null)
			fileTransferClient.stopConnect();
	}

	public synchronized void stop()
	{
		threadRunning = false;
		release();
		synchronized (lock)
		{
			lock.notifyAll();
		}
	}

	@Override
	public void run()
	{
		while (threadRunning)
		{
			LogTools.info("TCP客户端尝试连接到服务器...");

			if (jdbcTools != null)
				jdbcTools.reConnectInThread();
			createClient();

			if (client != null)
				LogTools.info("TCP客户端连接成功...");

			while (client != null && threadRunning)
			{
				try
				{
					client.update(200);
				} catch (IOException e)
				{
					release();
					LogTools.debug("TCP客户端连接异常!!!", e);
					break;
				}

				if (client != null)
					for (NetworkPacket packet : client.getReceivedPackets())
					{
						receivePacketOperators.operate(packet);
						if (enableUpdate && packet instanceof CommandPacket)
							update((CommandPacket) packet);
					}
			}
			if (!threadRunning)
				return;
			if (client != null)
				continue;

			synchronized (lock)
			{
				try
				{
					lock.wait(reTryTime);
				} catch (InterruptedException e)
				{
					e.printStackTrace();
				}
			}
		}
	}

	protected void update(CommandPacket cPacket)
	{
		UpdateTools.clientUpdateInThread(this, cPacket);
	}

	public boolean manualCheckUpdate()
	{
		enableUpdate = true;
		return UpdateTools.manualCheckUpdate(client);
	}

	public void autoCheckUpdate()
	{
		enableUpdate = true;
		autoUpdate = true;
		if (client != null)
			UpdateTools.autoCheckUpdate(client);
	}

	public void setAutoUpdate(boolean b)
	{
		autoUpdate = b;
		if (b)
			autoCheckUpdate();
	}

	public boolean isAutoUpdate()
	{
		return autoUpdate;
	}

	public void enableUpdate()
	{
		enableUpdate = true;
	}

	public void disableUpdate()
	{
		enableUpdate = false;
	}

	public String getServerIP()
	{
		return parameter.serverIP;
	}

	public int getServerPort()
	{
		return parameter.port;
	}

	public short getClientID()
	{
		if (client != null)
			return client.getClientID();
		return -1;
	}

	public String getClientDetail()
	{
		if (client != null)
			return client.getDetail();
		return "";
	}

	public synchronized void startFileTransferClient(int port,
			File receiveRootFolder, File sendRootFolder)
	{
		fileTransferPort = port;
		if (fileTransferClient != null)
		{
			fileTransferClient.setReceiveRootFolder(receiveRootFolder);
			fileTransferClient.setSendRootFolder(sendRootFolder);
			fileTransferClient.setHostAddress(parameter.serverIP, port);
		} else
		{
			fileTransferClient = new FileTransferClient(parameter.serverIP,
					port);
			if (client != null)
				client.setFileTransfer(fileTransferClient);
			fileTransferClient.setReceiveRootFolder(receiveRootFolder);
			fileTransferClient.setSendRootFolder(sendRootFolder);
		}
	}

	public FileTransferClient getFileTransferClient()
	{
		return fileTransferClient;
	}

	public File[] getReceivedFiles()
	{
		if (fileTransferClient != null)
			return fileTransferClient.getReceivedFiles();
		return null;
	}

	public void sendFiles(File... files)
	{
		if (fileTransferClient != null)
			try
			{
				fileTransferClient.sendFiles(files);
			} catch (IOException e)
			{
				e.printStackTrace();
			}
	}

	public void getFiles(String... fileNames)
	{
		if (fileTransferClient != null)
			fileTransferClient.getFiles(fileNames);
	}

	public File[] getFilesAndWait(int timeout, String... fileNames)
	{
		if (fileTransferClient != null)
			return fileTransferClient.getFilesAndWait(timeout, fileNames);
		return null;
	}

}
